/*
============================================================================
 Name        : ShoutcastReader.cpp
 Author      : 
 Version     :
 Copyright   : Your copyright notice
 Description : CShoutcastReader implementation
============================================================================
*/

#include "ShoutcastReader.h"

#include <stdlib.h>		//atoi
#include <string.h>		//memmove

// HTTP Response headers
_LIT8(KSCOKHeader, "200 OK\r\n");
_LIT8(KSCContentTypeTag, "content-type");
_LIT(KSCMimeTypeAudioMpeg, "audio/mpeg");
_LIT(KSCMimeTypeAudioAacp, "audio/aacp");
_LIT(KSCMimeTypeAudioAac, "audio/aac");
_LIT8(KSCIcyMetaintTag, "icy-metaint");
_LIT8(KSCIcyNameTag, "icy-name");
_LIT8(KSCIcyGenreTag, "icy-genre");
_LIT8(KSCIcyBrTag, "icy-br");
_LIT8(KShoutcastStreamTitle, "StreamTitle='");
_LIT8(KShoutcastHeaders, "User-Agent:ShtctVersificator/1.0\r\nAccept:*/*\r\nicy-metadata:1\r\nConnection:close\r\n");

CShoutcastReader::CShoutcastReader(MShoutcastReaderObserver& aObserver)
: iObserver(aObserver), iBufferToDecodePtr(NULL, 0, 0)
{
}

CShoutcastReader::~CShoutcastReader()
{
	iBufferToDecodePtr.Set(NULL, 0, 0);
}

CShoutcastReader* CShoutcastReader::NewLC(MShoutcastReaderObserver& aObserver)
{
	CShoutcastReader* self = new (ELeave)CShoutcastReader(aObserver);
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
}

CShoutcastReader* CShoutcastReader::NewL(MShoutcastReaderObserver& aObserver)
{
	CShoutcastReader* self = CShoutcastReader::NewLC(aObserver);
	CleanupStack::Pop(); // self;
	return self;
}

void CShoutcastReader::ConstructL()
{
	CStreamReader::ConstructL();
}

const TDesC8& CShoutcastReader::GetAdditionalHeadersL()
{
	return KShoutcastHeaders;
}

TDesC8& CShoutcastReader::ParseResponseL(TDesC8& aChunk)
{
	TUint8* bufferToDecode = const_cast<TUint8*>(aChunk.Ptr());
	TInt length = aChunk.Size();

	//Initialize pointer to buffer
	iBufferToDecodePtr.Set(bufferToDecode, length, length);
	TInt lastReadPos = 0;

	//Check if headers are sent or there is only data
	if (iBufferToDecodePtr.Find(KSCOKHeader) != KErrNotFound)
	{
		//There is headers. We parse them
		while (ETrue) // Parse the response header
		{
			//Search for \r\n
			TInt pos = iBufferToDecodePtr.Locate(10);
			if(pos == KErrNotFound)
			{
				//Data received without headers
				break;
			}
			
			if (pos <= 0 || iBufferToDecodePtr[pos - 1] != 13)
			{
				//Data received without headers
				break;
			}

			//Detected line to parse
			TPtr8 lineToDecodePtr(bufferToDecode + lastReadPos, pos + 1, pos + 1);

			//Parse line
			ParseLineL(lineToDecodePtr);

			//Remove line parsed from buffer
			lastReadPos += pos + 1;

			//Check if is the end of response headers
			if (pos == 1)
			{
				break;
			}

			//Set next line to parse
			iBufferToDecodePtr.Set(bufferToDecode + lastReadPos, iBufferToDecodePtr.Length() - pos - 1, iBufferToDecodePtr.Length() - pos - 1);
		}
	}

	//End of headers. We get received data and metadata (if any)
	TInt dataLength = GetReceivedDataL(bufferToDecode + lastReadPos, length - lastReadPos);

	//???GetAudioSettings();

	//Set new length (data without metadata)
	iBufferToDecodePtr.SetLength(dataLength);
	
	return iBufferToDecodePtr;
}

void CShoutcastReader::ParseLineL(TDesC8& aLine)
{
	TBuf<256> bufValue;

	//Parse line
	if (GetHeaderValueL(aLine, KSCContentTypeTag, &bufValue))
	{
		//MIME type of the stream
/*
		if (!bufValue.Compare(KSCMimeTypeAudioMpeg))
		{
			iDataType.Set(KMMFFourCCCodeMP3);
		}
		else if (!bufValue.Compare(KSCMimeTypeAudioAacp))
		{
			iDataType.Set(KMMFFourCCCodeAAC);
		}
		else if (!bufValue.Compare(KSCMimeTypeAudioAac))
		{
			iDataType.Set(KMMFFourCCCodeAAC);
		}
*/
		return;
	}

	if (GetHeaderValueL(aLine, KSCIcyMetaintTag, &iMetaint))
	{
		//Reset next metadata position
		iNextMetadataPos = iMetaint;

		return;
	}

	//Server name
	if (GetHeaderValueL(aLine, KSCIcyNameTag, &iServerName))
	{
		iObserver.OnServerNameChanged(iServerName);
		return;
	}

	//Bitrate
	if (GetHeaderValueL(aLine, KSCIcyBrTag, &iBitRate))	return;
}

TBool CShoutcastReader::GetHeaderValueL(TDesC8& aLine, const TDesC8& aKey, TDes* value)
{
	if (aLine.Find(aKey) == KErrNotFound) return EFalse;
	
	TInt pos = aLine.LocateReverse(':');
	if(pos == KErrNotFound) return EFalse;

	value->Copy(aLine.Right(aLine.Length() - pos - 1));
	value->Trim();

	return ETrue;
}

TBool CShoutcastReader::GetHeaderValueL(TDesC8& aLine, const TDesC8& aKey, TDes8* value)
{
	if (aLine.Find(aKey) == KErrNotFound) return EFalse;
	
	TInt pos = aLine.LocateReverse(':');
	if(pos == KErrNotFound) return EFalse;

	value->Copy(aLine.Right(aLine.Length() - pos - 1));
	value->Trim();

	return ETrue;
}

TBool CShoutcastReader::GetHeaderValueL(TDesC8& aLine, const TDesC8& aKey, TInt* value)
{
	TBuf8<10> buffer;
	if (!GetHeaderValueL(aLine, aKey, &buffer)) return EFalse;

	*value = atoi((const char *)buffer.PtrZ());
	
	return ETrue;
}

TInt CShoutcastReader::GetReceivedDataL(TUint8* aBufferToDecode, TInt aLength)
{
	while (iNextMetadataPos < aLength)
	{
		//Skip every piece of data and get metadata
		TInt metadataLength = 16 * aBufferToDecode[iNextMetadataPos];

		if (metadataLength > 0)
		{
			//TODO: Check if metadata is broken
			//if (pos + metadataLength > aLength)
			TPtr8 bufferMetadataPtr(aBufferToDecode + iNextMetadataPos, metadataLength, metadataLength);
			ParseMetadataL(bufferMetadataPtr);
		}
		
		//Remove metadata
		memmove(aBufferToDecode + iNextMetadataPos, aBufferToDecode + iNextMetadataPos + metadataLength + 1, aLength - iNextMetadataPos - metadataLength - 1);
		aLength -= (metadataLength + 1);

		iNextMetadataPos += iMetaint;
	}
	iNextMetadataPos -= aLength;
	
	//Returns data length
	return aLength;
}

void CShoutcastReader::ParseMetadataL(TDesC8& aMetadata)
{
	TInt pos1 = aMetadata.Find(KShoutcastStreamTitle);
	TInt pos2 = aMetadata.Find(_L8("';"));

	if (pos1 == KErrNotFound || pos2 == KErrNotFound) return;
	
	//Metadata found. Copy stream title
	iMetadata.Copy(aMetadata.Mid(pos1 + 13, pos2 - pos1 - 13));

	//Notify to observer
	iObserver.OnMetadataChanged(iMetadata);
}

const TDesC& CShoutcastReader::GetServerName()
{
	return iServerName;
}